const router = require('koa-router')();
const path = require('path');
const fse = require('fs-extra');

// 提取文件后缀名
const extractExt = (filename) => filename.slice(filename.lastIndexOf('.'), filename.length);
// 大文件存储目录
const UPLOAD_DIR = path.resolve(__dirname, '..', 'target');

// 返回已经上传切片名列表
const createUploadedList = async (fileHash) =>
  fse.existsSync(path.resolve(UPLOAD_DIR, `${fileHash}-chunks`)) ? await fse.readdir(path.resolve(UPLOAD_DIR, `${fileHash}-chunks`)) : [];

/**
 * 针对 path 创建 readStream 并写入 writeStream,写入完成之后删除文件
 * @param {String} path
 * @param {String} writeStream
 */
const pipeStream = (path, writeStream) =>
  new Promise((resolve) => {
    const readStream = fse.createReadStream(path);
    readStream.on('end', () => {
      // fse.unlinkSync(path);
      resolve();
    });
    readStream.pipe(writeStream);
  });

/**
 * 读取所有的 chunk 合并到 filePath 中
 * @param {String} filePath 文件存储路径
 * @param {String} chunkDir chunk存储文件夹名称
 * @param {String} size 每一个chunk的大小
 */
async function mergeFileChunk(filePath, chunkDir, size) {
  // 获取chunk列表
  const chunkPaths = await fse.readdir(chunkDir);
  // 根据切片下标进行排序  否则直接读取目录的获得的顺序可能会错乱
  chunkPaths.sort((a, b) => a.split('-')[1] - b.split('-')[1]);
  await Promise.all(
    chunkPaths.map((chunkPath, index) =>
      pipeStream(
        path.resolve(chunkDir, chunkPath),
        // 指定位置创建可写流
        fse.createWriteStream(filePath, {
          start: index * size,
          end: (index + 1) * size,
        })
      )
    )
  );
  // fse.rmdirSync(chunkDir); // 合并后删除保存切片的目录
}

// 上传 chunk
router.post('/upload-chunk', async (ctx, next) => {
  // 模拟请求失败
  // if (Math.random() > 0.5) {
  // ctx.status = 500;
  // return;
  // }
  // 请求参数在这
  // hash是当前chunk的唯一值   fileHash是文件的hash
  const { chunkHash, fileHash } = ctx.request.body;
  // 文件在这
  const { chunk } = ctx.request.files;
  // chunks保存路径是 filename-chunks (使用hash可以废弃了)
  // const chunkDir = path.resolve(UPLOAD_DIR, `${filename}-chunks`);
  const chunkDir = path.resolve(UPLOAD_DIR, `${fileHash}-chunks`);
  // 切片目录不存在，创建切片目录
  if (!fse.existsSync(chunkDir)) {
    await fse.mkdirs(chunkDir);
  }

  // fs-extra 专用方法，类似 fs.rename 并且跨平台
  // fs-extra 的 rename 方法 windows 平台会有权限问题
  // https://github.com/meteor/meteor/issues/7852#issuecomment-255767835
  await fse.move(chunk.path, `${chunkDir}/${chunkHash}`);
  ctx.body = { code: 0, data: '', msg: '上传成功' };
});

// 合并
router.post('/merge', async (ctx, next) => {
  const { filename, fileSize, size, hash } = ctx.request.body;
  // filePath 是文件存储路径
  const ext = extractExt(filename);
  const filePath = path.resolve(UPLOAD_DIR, `${hash}${ext}`);
  const chunkDir = path.resolve(UPLOAD_DIR, `${hash}-chunks`);
  await mergeFileChunk(filePath, chunkDir, size);
  ctx.body = { code: 0, data: '', msg: '合并成功' };
});

// 验证是否存在
router.post('/verify', async (ctx, next) => {
  const { filename, fileHash } = ctx.request.body;
  console.log('==>', filename);
  console.log('==>', fileHash);
  let shouldUpload = true;
  let msg = '文件不存在，需要上传';
  const ext = extractExt(filename);
  const filePath = path.resolve(UPLOAD_DIR, `${fileHash}${ext}`);
  if (fse.existsSync(filePath)) {
    shouldUpload = false;
    msg = '文件存在，不需要上传';
  }
  ctx.body = { code: 0, data: { shouldUpload, uploadedList: await createUploadedList(fileHash) }, msg };
});

module.exports = router;
/**
 * 有一个坑  就是存文件切片的文件夹名称和最后保存文件的文件名称是一致的
 * 系统是不允许有同名的文件和文件夹，就导致合并chunk的时候会卡住 也不报错
 *
 * 特此规定，保存chunk的文件就名称后面需要添加 -chunk后缀以和文件名区分
 */
